package com.test.platform.module.perf.service;

import com.alibaba.fastjson.JSONObject;
import com.test.common.component.RedisComponent;
import com.test.common.entity.perf.*;
import com.test.common.entity.perf.inner.ErrorMessageEntity;
import com.test.common.entity.perf.inner.TransactionMessageEntity;
import com.test.common.enums.perf.PerfReportStatEnum;
import com.test.common.repository.perf.PerfReportBaseRepository;
import com.test.common.repository.perf.PerfReportRepository;
import com.test.common.repository.perf.PerfScriptRepository;
import com.test.common.repository.perf.PerfTaskRepository;
import com.test.common.response.BizException;
import com.test.platform.module.perf.Config;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import java.util.*;

@Slf4j
@Service
public class PerfReportService {

    @Resource(name = "perf_config")
    private Config config;

    @Resource
    private RedisComponent redisComponent;

    @Resource
    private PerfReportRepository perfReportRepository;

    @Resource
    private PerfReportBaseRepository perfReportBaseRepository;

    @Resource
    private PerfTaskRepository perfTaskRepository;

    @Resource
    private PerfScriptRepository perfScriptRepository;

    private final RestTemplate restTemplate = new RestTemplate();

    public Object getReportDetail(int id, int count, int interval) {

        Optional<PerfReportEntity> perfReportEntityOptional = perfReportRepository.findById(id);
        if (perfReportEntityOptional.isPresent()) {

            HashMap<String, Object> resultMap = new HashMap<>();

            PerfReportEntity perfReportEntity = perfReportEntityOptional.get();
            log.info("查询报告详情：{} - {}", perfReportEntity.getId(), perfReportEntity.getUuid());

            long start = System.currentTimeMillis();

            // 查询所有的时候不排序不翻转
            List<PerfReportBaseEntity> perfReportBaseEntityListOriginal = null;
            if (count == 999999999) {
                perfReportBaseEntityListOriginal = perfReportBaseRepository.findAllByReportId(
                        perfReportEntity.getUuid());
            } else {
                perfReportBaseEntityListOriginal = perfReportBaseRepository.findAllByReportId(
                        perfReportEntity.getUuid(), PageRequest.of(0, count, Sort.by("timeStampList").descending()));
                // 翻转List
                Collections.reverse(perfReportBaseEntityListOriginal);
            }

            log.info("数据查询耗时：{}ms", System.currentTimeMillis() - start);
            start = System.currentTimeMillis();

            // 改为混合指定间隔内的数据，然后取平均值
            List<PerfReportBaseEntity> perfReportBaseEntityList = new ArrayList<>();
            for (int i = 0; i < perfReportBaseEntityListOriginal.size(); ) {
                PerfReportBaseEntity merge = perfReportBaseEntityListOriginal.get(i++);
                // 因为外面已经取一个了，所以这里要从1开始遍历
                for (int j = 1; j < interval && i < perfReportBaseEntityListOriginal.size(); j++, i++) {
                    merge.merge(perfReportBaseEntityListOriginal.get(i));
                }
                perfReportBaseEntityList.add(merge);
            }
            perfReportBaseEntityListOriginal = null; // 数组很大，尽早释放

            Set<String> labelSet = new HashSet<>();
            ArrayList<HashMap<String, Object>> transactionMessageEntities = new ArrayList<>();
            Map<String, ErrorMessageEntity> errorMessageEntityMap = new HashMap<>();

            int successCount = 0;
            int failCount = 0;

            // 运行中只显示最新数据，运行结束统计全局数据
            boolean summaryContral = perfReportEntity.getStatus() == PerfReportStatEnum.RUNNING;

            int summaryCount = 0;
            int currentVusers = 0;
            int currentQps = 0;
            long currentSend = 0L;
            long currentReceive = 0L;
            double currentAvgDelay = 0.0;
            double currentP99Delay = 0.0;

            for (int i = 0; i < perfReportBaseEntityList.size(); i++) {

                PerfReportBaseEntity perfReportBaseEntity = perfReportBaseEntityList.get(i);

                // 统计QPS信息
                List<TransactionMessageEntity> transactionMessageEntiyList = perfReportBaseEntity
                        .getTransactionMessageList();
                HashMap<String, Object> tmpMap = new HashMap<String, Object>();
                for (TransactionMessageEntity transactionMessageEntity : transactionMessageEntiyList) {
                    transactionMessageEntity.addToMap(tmpMap);
                    labelSet.add(transactionMessageEntity.getLabel());
                    if (transactionMessageEntity.getLabel().equals("total")) {
                        successCount += transactionMessageEntity.getSuccessCount();
                        failCount += transactionMessageEntity.getFailCount();

                        if (summaryContral) {
                            currentVusers = transactionMessageEntity.getThreads();
                            currentQps = transactionMessageEntity.getQps();
                            currentSend = transactionMessageEntity.getSendBytes();
                            currentReceive = transactionMessageEntity.getReceiveByte();
                            currentAvgDelay = transactionMessageEntity.getAvgDealy();
                            currentP99Delay = transactionMessageEntity.getLevel_3_Dealy();
                            summaryCount = 1;
                        } else {
                            if (i > perfReportBaseEntityList.size() / 2) {
                                currentVusers = Math.max(currentVusers, transactionMessageEntity.getThreads());
                                currentQps = currentQps + transactionMessageEntity.getQps();
                                currentSend = currentSend + transactionMessageEntity.getSendBytes();
                                currentReceive = currentReceive + transactionMessageEntity.getReceiveByte();
                                currentAvgDelay = currentAvgDelay + transactionMessageEntity.getAvgDealy();
                                currentP99Delay = currentP99Delay + transactionMessageEntity.getLevel_3_Dealy();
                                summaryCount++;
                            }
                        }

                    }
                }
                transactionMessageEntities.add(tmpMap);

                // 统计Error信息
                if (!perfReportBaseEntity.getErrorMessageMap().isEmpty()) {
                    for (Map.Entry<String, ErrorMessageEntity> entry : perfReportBaseEntity.getErrorMessageMap()
                            .entrySet()) {
                        if (errorMessageEntityMap.containsKey(entry.getKey())) {
                            errorMessageEntityMap.get(entry.getKey()).add(entry.getValue());
//							entry.getValue().setName(UUID.randomUUID().toString());
//							errorMessageEntityMap.get(UUID.randomUUID().toString()).add(entry.getValue());
                        } else {
                            errorMessageEntityMap.put(entry.getKey(), entry.getValue());
//							entry.getValue().setName(UUID.randomUUID().toString());
//							errorMessageEntityMap.put(UUID.randomUUID().toString(), entry.getValue());
                        }
                    }
                }
            }

            // 计算平均值
            if (summaryCount != 0) {
                currentQps = currentQps / summaryCount;
                currentSend = currentSend / summaryCount;
                currentReceive = currentReceive / summaryCount;
                currentAvgDelay = currentAvgDelay / summaryCount;
                currentP99Delay = currentP99Delay / summaryCount;
            }

            ArrayList<String> qps_threads = new ArrayList<String>();
            qps_threads.add("timeStamp");
            labelSet.forEach(label -> {
                qps_threads.add(label + "_qps");
                qps_threads.add(label + "_threads");
            });

            ArrayList<String> delayAvg = new ArrayList<String>();
            delayAvg.add("timeStamp");
            labelSet.forEach(label -> {
                delayAvg.add(label + "_avgDealy");
            });

            ArrayList<String> delay50 = new ArrayList<String>();
            delay50.add("timeStamp");
            labelSet.forEach(label -> {
                delay50.add(label + "_50%_Dealy");
            });

            ArrayList<String> delay90 = new ArrayList<String>();
            delay90.add("timeStamp");
            labelSet.forEach(label -> {
                delay90.add(label + "_90%_Dealy");
            });

            ArrayList<String> delay99 = new ArrayList<String>();
            delay99.add("timeStamp");
            labelSet.forEach(label -> {
                delay99.add(label + "_99%_Dealy");
            });

            ArrayList<String> delayMin = new ArrayList<String>();
            delayMin.add("timeStamp");
            labelSet.forEach(label -> {
                delayMin.add(label + "_minDealy");
            });

            ArrayList<String> delayMax = new ArrayList<String>();
            delayMax.add("timeStamp");
            labelSet.forEach(label -> {
                delayMax.add(label + "_maxDealy");
            });

            ArrayList<String> send_receive = new ArrayList<String>();
            send_receive.add("timeStamp");
            labelSet.forEach(label -> {
                send_receive.add(label + "_sendBytes");
                send_receive.add(label + "_receiveByte");
            });

            String taskName = "未找到对应任务";
            String scriptName = "未找到对应脚本";

            Optional<PerfTaskEntity> perfTaskEntity = perfTaskRepository.findById(perfReportEntity.getTaskId());
            Optional<PerfScriptEntity> perfScriptEntity = perfScriptRepository.findById(perfReportEntity.getScriptId());


            if (perfTaskEntity.isPresent()) {
                taskName = perfTaskEntity.get().getName();
            }

            if (perfScriptEntity.isPresent()) {
                scriptName = perfScriptEntity.get().getName();
            }

            log.info("数据计算耗时：{}ms", System.currentTimeMillis() - start);

            resultMap.put("data", transactionMessageEntities);
            resultMap.put("qps_threads", qps_threads);
            resultMap.put("delayAvg", delayAvg);
            resultMap.put("delay50", delay50);
            resultMap.put("delay90", delay90);
            resultMap.put("delay99", delay99);
            resultMap.put("delayMin", delayMin);
            resultMap.put("delayMax", delayMax);
            resultMap.put("send_receive", send_receive);
            resultMap.put("percentAge", perfReportEntity.getPercentAge());
            resultMap.put("errorMessage", errorMessageEntityMap.values());
            resultMap.put("successCount", successCount);
            resultMap.put("failCount", failCount);
            resultMap.put("taskName", taskName);
            resultMap.put("scriptName", scriptName);
            resultMap.put("startTime", perfReportEntity.getStartTimeStr());
            resultMap.put("endTime", perfReportEntity.getEndTimeStr());
            resultMap.put("status", perfReportEntity.getStatus());
            resultMap.put("remark", perfReportEntity.getRemark());
            resultMap.put("currentVusers", currentVusers);
            resultMap.put("currentQps", currentQps);
            resultMap.put("currentSend", currentSend);
            resultMap.put("currentReceive", currentReceive);
            resultMap.put("currentAvgDelay", currentAvgDelay);
            resultMap.put("currentP99Delay", currentP99Delay);

            // 在这里更新一下最终的QPS
            perfReportEntity.setQps(currentQps);
            perfReportRepository.save(perfReportEntity);

            return resultMap;
        } else {
            throw new BizException("找不到指定的任务：" + id);
        }
    }

    public Object searchAll(int pageNo, int pageSize) {
        Page<PerfReportEntity> resPage = perfReportRepository.findAll(PageRequest.of(pageNo - 1, pageSize, new Sort(Sort.Direction.DESC, "startTime")));
        if (resPage.getSize() > 0) {
            return resPage;
        } else {
            throw new BizException("没有查询到任何数据");
        }
    }

    public Object countByStatus() {
        HashMap<String, Object> resultMap = new HashMap<>();
        resultMap.put("runningTasks", perfReportRepository.countByStatus(PerfReportStatEnum.RUNNING));
        resultMap.put("waitingTasks", perfReportRepository.countByStatus(PerfReportStatEnum.WAITING));
        resultMap.put("finishedTasks", perfReportRepository.countByStatus(PerfReportStatEnum.FINISHED));
        resultMap.put("errorTasks", perfReportRepository.countByStatus(PerfReportStatEnum.ERROR));
        return resultMap;
    }

    /**
     * 这个方法加上同步锁，只能单线程执行
     * 目前设置的，同一个任务中的脚本只能串行执行，不同任务中的脚本可以并行执行
     */
    @Scheduled(fixedDelay = 5000, initialDelay = 3000)
    public synchronized void scanner() {
        log.debug("启动扫描");

        List<PerfReportEntity> waitingList = perfReportRepository.findAllByStatus(PerfReportStatEnum.WAITING);
        List<PerfReportEntity> runningList = perfReportRepository.findAllByStatus(PerfReportStatEnum.RUNNING);

        Set<Integer> currentSet = new HashSet<>();

        // 同一个任务中的脚本只能串行执行，不同任务的脚本可以并行执行
        for (PerfReportEntity waitting : waitingList) {

            if (currentSet.contains(waitting.getTaskId())) {
                continue;
            }

            if (runningList.size() == 0) {
                currentSet.add(waitting.getTaskId());
                notifyStart(waitting);
                waitting.setStatus(PerfReportStatEnum.RUNNING);
                waitting.setStartTime(new Date());
                perfReportRepository.save(waitting);
            } else if (runningList.size() > 0) {
                runningList.stream().forEach(running -> {
                    // todo 后续再设计一下同一个任务或者不同任务的并行策略
                    if (waitting.getTaskId() == running.getTaskId()) {
                        log.info("当前任务`{}`已有脚本`{}`执行中，跳过脚本`{}`", waitting.getTaskName(), running.getScriptName(), waitting.getScriptName());
                    } else {
                        currentSet.add(waitting.getTaskId());
                        notifyStart(waitting);
                        waitting.setStatus(PerfReportStatEnum.RUNNING);
                        waitting.setStartTime(new Date());
                        perfReportRepository.save(waitting);
                    }
                });
            }

        }

        log.debug("结束扫描");
    }

    private void notifyStart(PerfReportEntity waitting) {
        // 给执行机发送执行命令
        List<PerfAgentEntity> agentEntityList = waitting.getAgentEntityList();
        for (PerfAgentEntity agentEntity : agentEntityList) {
            // 这个地方直接启动新线程了
            new Thread(() -> {
                JSONObject json = restTemplate.getForObject(agentEntity.getUrl() + "/perf/agent/startTask?reportId=" + waitting.getId(), JSONObject.class);
                log.info("执行结果：{}", json);
            }).start();
        }
    }

    public Object finish(String uuid) {
        Optional<PerfReportEntity> perfReportEntityOptional = perfReportRepository.findByUuid(uuid);
        if (perfReportEntityOptional.isPresent()) {
            PerfReportEntity perfReportEntity = perfReportEntityOptional.get();

            perfReportEntity.setStatus(PerfReportStatEnum.FINISHED);
            perfReportEntity.setEndTime(new Date());
            perfReportEntity.setPercentAge(100);

            perfReportRepository.save(perfReportEntity);
            return "success";
        } else {
            throw new BizException("找不到报告对象");
        }
    }

    public Object stop(int reportId) {
        Optional<PerfReportEntity> perfReportEntityOptional = perfReportRepository.findById(reportId);
        if (perfReportEntityOptional.isPresent()) {
            PerfReportEntity perfReportEntity = perfReportEntityOptional.get();
            List<PerfAgentEntity> agentEntityList = perfReportEntity.getAgentEntityList();

            int i = 0;

            for (PerfAgentEntity perfAgentEntity : agentEntityList) {
                JSONObject json = restTemplate.getForObject(perfAgentEntity.getUrl() + "/perf/agent/stopTask?reportUuid=" + perfReportEntity.getUuid(), JSONObject.class);
                log.info("执行结果：{}", json);
                if (json != null && json.getIntValue("code") == 200) {
                    i++;
                }
            }

            if (i == agentEntityList.size()) {
                perfReportEntity.setStatus(PerfReportStatEnum.STOPPED);
                perfReportEntity.setEndTime(new Date());
                perfReportRepository.save(perfReportEntity);
                return "执行机进程已全部关闭";
            } else {
                return "总执行机数：" + agentEntityList.size() + "台，已成功关闭：台" + i;
            }
        } else {
            throw new BizException("找不到报告对象");
        }
    }

    public Object reExecution(int reportId, int users, int qps, String remark) {
        Optional<PerfReportEntity> perfReportEntityOptional = perfReportRepository.findById(reportId);
        if (perfReportEntityOptional.isPresent()) {
            PerfReportEntity oldEntity = perfReportEntityOptional.get();

            users = users <= 0 ? oldEntity.getSetUsers() : users;
            qps = qps <= 0 ? oldEntity.getSetQps() : qps;

            PerfReportEntity newEntity = new PerfReportEntity(oldEntity.getTaskId(), oldEntity.getTaskName(), oldEntity.getScriptId(), oldEntity.getScriptName(), oldEntity.getRampTime(), oldEntity.getDuration(), new Date(), qps, users, remark, new ArrayList<>(oldEntity.getAgentEntityList()));
            perfReportRepository.save(newEntity);
            return "成功，已加入待执行队列";
        } else {
            throw new BizException("找不到报告对象");
        }
    }

    public Object delete(int reportId) {
        Optional<PerfReportEntity> perfReportEntityOptional = perfReportRepository.findById(reportId);
        if (perfReportEntityOptional.isPresent()) {
            PerfReportEntity perfReportEntity = perfReportEntityOptional.get();

            perfReportEntity.setAgentEntityList(null);
            perfReportRepository.deleteById(reportId);
            perfReportBaseRepository.deleteAllByReportId(perfReportEntity.getUuid());

            return "删除成功";
        } else {
            throw new BizException("找不到报告对象");
        }
    }
}
